/*
 * Copyright (c) 2023-2025 elsfs Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.elsfs.cloud.common.mybatis.ext.sql.support;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.CastExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.NotExpression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.SignedExpression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.conditional.XorExpression;
import net.sf.jsqlparser.expression.operators.relational.Between;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.GreaterThan;
import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.expression.operators.relational.MinorThan;
import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals;
import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.parser.feature.Feature;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.util.validation.ParseException;
import net.sf.jsqlparser.util.validation.Validation;
import net.sf.jsqlparser.util.validation.ValidationCapability;
import net.sf.jsqlparser.util.validation.ValidationError;
import net.sf.jsqlparser.util.validation.ValidationException;
import net.sf.jsqlparser.util.validation.feature.FeaturesAllowed;
import net.sf.jsqlparser.util.validation.feature.MySqlVersion;
import net.sf.jsqlparser.util.validation.feature.PostgresqlVersion;
import org.apache.commons.compress.utils.Sets;
import org.elsfs.cloud.common.util.lang.ArrayUtils;
import org.elsfs.cloud.common.util.lang.StringUtils;

/** SQL工具类 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SqlUtils {

  /** 查询SQL特性集 */
  private static final Set<ValidationCapability> QUERY_SQL_FEATURES =
      Sets.newHashSet(
          MySqlVersion.V8_0,
          PostgresqlVersion.V12,
          FeaturesAllowed.SELECT.copy().add(Feature.jdbcParameter, Feature.jdbcNamedParameter));

  /**
   * 处理SQL参数值
   *
   * @param value 参数值
   * @return 处理后的参数
   */
  public static String handleSqlParameterValue(Object value) {
    if (value instanceof CharSequence) {
      return "'" + value + "'";
    } else if (value instanceof Enum<?> enumeration) {
      return "'" + enumeration.name() + "'";
    } else if (value instanceof LocalDateTime
        || value instanceof LocalDate
        || value instanceof LocalTime) {
      return "'" + value + "'";
    } else if (value == null) {
      return "NULL";
    } else {
      return StringUtils.str(value, StandardCharsets.UTF_8);
    }
  }

  /**
   * 处理SQL表达式
   *
   * @param sqlExpression SQL表达式
   * @param args SQL表达式参数
   * @return 处理参数后的SQL表达式
   */
  public static String handleSqlExpression(String sqlExpression, Object... args) {
    if (StringUtils.isBlank(sqlExpression) || ArrayUtils.isEmpty(args)) {
      return sqlExpression;
    }

    Object[] processedArgs = new Object[args.length];
    for (int i = 0; i < args.length; i++) {
      processedArgs[i] = handleSqlParameterValue(args[i]);
    }

    return String.format(sqlExpression, processedArgs);
  }

  /**
   * 解析条件SQL列
   *
   * @param conditionSql 条件SQL
   * @return 条件列集合
   */
  public static Set<Column> parseConditionSqlColumns(String conditionSql) {
    try {
      Expression expression = CCJSqlParserUtil.parseCondExpression(conditionSql);
      WhereConditionExpressionVisitor expressionVisitor = new WhereConditionExpressionVisitor();
      expression.accept(expressionVisitor);

      return expressionVisitor.columns;
    } catch (JSQLParserException e) {
      throw new MybatisPlusException("解析条件SQL条件列出现异常：" + e.getMessage(), e);
    }
  }

  /**
   * 解析条件SQL列名
   *
   * @param conditionSql 条件SQL
   * @return 条件列名集合
   */
  public static Set<String> parseConditionSqlColumnNames(String conditionSql) {
    return parseConditionSqlColumns(conditionSql).stream()
        .map(Column::getFullyQualifiedName)
        .collect(Collectors.toSet());
  }

  /**
   * 验证查询SQL
   *
   * @param sql SQL语句
   * @param errorTip 错误提示
   */
  public static void assertIsQuerySql(String sql, String errorTip) {
    List<ValidationError> validationErrors = Validation.validate(QUERY_SQL_FEATURES, sql);

    for (ValidationError validationError : validationErrors) {
      for (ValidationException error : validationError.getErrors()) {
        if (error instanceof ParseException) {
          // 语法错误
          LOGGER.warn("{}SQL【{}】存在语法错误", errorTip, sql, error);
          throw new MybatisPlusException(error + "SQL存在语法错误" + errorTip);
        } else {
          LOGGER.warn("{}SQL【{}】不受允许：", errorTip, sql, error);
          throw new MybatisPlusException("不允许的{}SQL：" + errorTip + error.getMessage());
        }
      }
    }
  }

  /** 条件表达式访问器 */
  static class WhereConditionExpressionVisitor extends ExpressionVisitorAdapter {

    /** 条件字段列名 */
    private final Set<Column> columns = Sets.newHashSet();

    /**
     * 访问函数表达式
     *
     * @param function 函数表达式
     */
    @Override
    public void visit(Function function) {
      ExpressionList parameters = function.getParameters();
      if (parameters == null) {
        return;
      }

      for (Expression expression : parameters.getExpressions()) {
        expression.accept(this);
      }
    }

    /**
     * 方法圆括号表达式
     *
     * @param parenthesis 圆括号表达式
     */
    @Override
    public void visit(Parenthesis parenthesis) {
      parenthesis.getExpression().accept(this);
    }

    /**
     * 访问And表达式
     *
     * @param expr And表达式
     */
    @Override
    public void visit(AndExpression expr) {
      expr.getLeftExpression().accept(this);
      expr.getRightExpression().accept(this);
    }

    /**
     * 访问Or表达式
     *
     * @param expr Or表达式
     */
    @Override
    public void visit(OrExpression expr) {
      expr.getLeftExpression().accept(this);
      expr.getRightExpression().accept(this);
    }

    /**
     * 访问Xor表达式
     *
     * @param expr Xor表达式
     */
    @Override
    public void visit(XorExpression expr) {
      expr.getLeftExpression().accept(this);
      expr.getRightExpression().accept(this);
    }

    /**
     * 访问Between表达式
     *
     * @param expr Between表达式
     */
    @Override
    public void visit(Between expr) {
      expr.getLeftExpression().accept(this);
      expr.getBetweenExpressionStart().accept(this);
      expr.getBetweenExpressionEnd().accept(this);
    }

    /**
     * 访问等于表达式
     *
     * @param expr 等于表达式
     */
    @Override
    public void visit(EqualsTo expr) {
      expr.getLeftExpression().accept(this);
      expr.getRightExpression().accept(this);
    }

    /**
     * 访问大于表达式
     *
     * @param expr 大于表达式
     */
    @Override
    public void visit(GreaterThan expr) {
      expr.getLeftExpression().accept(this);
      expr.getRightExpression().accept(this);
    }

    /**
     * 访问大于等于表达式
     *
     * @param expr 大于等于表达式
     */
    @Override
    public void visit(GreaterThanEquals expr) {
      expr.getLeftExpression().accept(this);
      expr.getRightExpression().accept(this);
    }

    /**
     * 访问In表达式
     *
     * @param expr In表达式
     */
    @Override
    public void visit(InExpression expr) {
      expr.getLeftExpression().accept(this);
      if (expr.getRightItemsList() != null) {
        expr.getRightItemsList().accept(this);
      } else if (expr.getRightExpression() != null) {
        expr.getRightExpression().accept(this);
      }
    }

    /**
     * 访问为空表达式
     *
     * @param expr 为空表达式
     */
    @Override
    public void visit(IsNullExpression expr) {
      expr.getLeftExpression().accept(this);
    }

    /**
     * 访问模糊匹配表达式
     *
     * @param expr 模糊匹配表达式
     */
    @Override
    public void visit(LikeExpression expr) {
      expr.getLeftExpression().accept(this);
      expr.getRightExpression().accept(this);
    }

    /**
     * 访问小于表达式
     *
     * @param expr 小于表达式
     */
    @Override
    public void visit(MinorThan expr) {
      expr.getLeftExpression().accept(this);
      expr.getRightExpression().accept(this);
    }

    /**
     * 访问小于等于表达式
     *
     * @param expr 小于等于表达式
     */
    @Override
    public void visit(MinorThanEquals expr) {
      expr.getLeftExpression().accept(this);
      expr.getRightExpression().accept(this);
    }

    /**
     * 访问不等于表达式
     *
     * @param expr 不等于表达式
     */
    @Override
    public void visit(NotEqualsTo expr) {
      expr.getLeftExpression().accept(this);
      expr.getRightExpression().accept(this);
    }

    /**
     * 访问列表达式
     *
     * @param column 列表达式
     */
    @Override
    public void visit(Column column) {
      this.columns.add(column);
    }

    /**
     * 访问数据类型转换表达式
     *
     * @param expr 数据类型转换表达式
     */
    @Override
    public void visit(CastExpression expr) {
      expr.getLeftExpression().accept(this);
    }

    /**
     * 访问取反表达式
     *
     * @param notExpr 取反表达式
     */
    @Override
    public void visit(NotExpression notExpr) {
      notExpr.getExpression().accept(this);
    }

    /**
     * 访问符号表达式
     *
     * @param expr 符号表达式
     */
    @Override
    public void visit(SignedExpression expr) {
      expr.getExpression().accept(this);
    }
  }
}
